package org.rosenvold.spring.convention; /* * Copyright 2011 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.*; import org.springframework.context.annotation.*; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ConventionBeanFactory extends DefaultListableBeanFactory { private final NameToClassResolver nameToClassResolver; private final CandidateEvaluator candidateEvaluator; private final Map<String, AnnotatedBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, AnnotatedBeanDefinition>(); private final Map<Class, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap<Class, RootBeanDefinition>(); private final Map<String, Class> cachedConventionResolutions = new ConcurrentHashMap<String, Class>(); private final Map<Class, Object> resolvableDependenciesLocalCache = new HashMap<Class, Object>(); /* Maps by type to bean names */ private final Map<Class, String[]> byTypeMappingSingletonsEager = new ConcurrentHashMap<Class, String[]>(); private final Map<Class, String[]> byTypeMappingNonSingletonsEager = new ConcurrentHashMap<Class, String[]>(); private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver); private BeanDefinitionDefaults beanDefinitionDefaults = new BeanDefinitionDefaults(); private final QualifierAnnotationAutowireCandidateResolver qualifierAnnotationAutowireCandidateResolver = new QualifierAnnotationAutowireCandidateResolver(); private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver(); private final String[] nothing = new String[]{}; public ConventionBeanFactory(NameToClassResolver beanClassResolver, CandidateEvaluator candidateEvaluator) { this.nameToClassResolver = beanClassResolver; this.candidateEvaluator = candidateEvaluator; } private void clearTypeBasedCaches() { byTypeMappingSingletonsEager.clear(); byTypeMappingNonSingletonsEager.clear(); } @Override public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { if (type == null || !allowEagerInit) { return getBeanNamesForTypeImpl(type, includeNonSingletons, allowEagerInit); } Map<Class, String[]> cache = includeNonSingletons ? byTypeMappingNonSingletonsEager : byTypeMappingSingletonsEager; String[] resolvedBeanNames = cache.get(type); if (resolvedBeanNames != null) { return resolvedBeanNames; } resolvedBeanNames = getBeanNamesForTypeImpl(type, includeNonSingletons, allowEagerInit); cache.put(type, resolvedBeanNames); return resolvedBeanNames; } public synchronized String[] getBeanNamesForTypeImpl(Class type, boolean includeNonSingletons, boolean allowEagerInit) { // LBF, local only. final Class cacheEntry = getCacheEntry(type); if (cacheEntry == null) { final String[] beanNamesForType = super.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); if (beanNamesForType.length > 0) return beanNamesForType; final Class aClass = resolveImplClass(type.getName()); if (aClass != null) { return new String[]{aClass.getName()}; } return nothing; } else { if (isCacheMiss(cacheEntry)) { return nothing; } else { return new String[]{cacheEntry.getName()}; } } } @SuppressWarnings("unchecked") @Override public synchronized <T> T getBean(Class<T> requiredType) throws BeansException { final Class cacheEntry = getCacheEntry(requiredType); if (cacheEntry == null) { if (super.getBeanNamesForType(requiredType).length > 0) { return super.getBean(requiredType); } final Class aClass = resolveClass(requiredType); return (T) instantiate(aClass); } else { return isCacheMiss(cacheEntry) ? null : (T) instantiate(cacheEntry); } } @Override public synchronized Object getBean(String name) throws BeansException { setupConventionBeanIfMissing(name); return super.getBean(name); } @Override public synchronized <T> T getBean(String name, Class<T> tClass) throws BeansException { setupConventionBeanIfMissing(name); return super.getBean(name, tClass); } private synchronized void registerByDirectNameToClassMapping(String name) { final Class<?> type = getResolvedType(name); registerBeanByResolvedType(name, type); } @Override public synchronized Object getBean(String name, Object... objects) throws BeansException { setupConventionBeanIfMissing(name); return super.getBean(name, objects); } @Override public synchronized boolean containsBean(String name) { setupConventionBeanIfMissing(name); return super.containsBean(name); } @Override public synchronized boolean isSingleton(String name) throws NoSuchBeanDefinitionException { setupConventionBeanIfMissing(name); return super.isSingleton(name); } @Override public synchronized boolean isPrototype(String name) throws NoSuchBeanDefinitionException { setupConventionBeanIfMissing(name); return super.isPrototype(name); } private synchronized String getAnnotatedScope(Class<?> type) { if (type != null) { final Scope annotation = type.getAnnotation(Scope.class); if (annotation != null) { return annotation.value(); } } return AbstractBeanDefinition.SCOPE_DEFAULT; } @Override public synchronized boolean isTypeMatch(String name, Class aClass) throws NoSuchBeanDefinitionException { setupConventionBeanIfMissing(name); return super.isTypeMatch(name, aClass); } @Override public synchronized Class<?> getType(String name) throws NoSuchBeanDefinitionException { return super.getType(name); } @Override public synchronized String[] getAliases(String name) { setupConventionBeanIfMissing(name); return super.getAliases(name); } @Override public synchronized boolean containsBeanDefinition(String beanName) { // LBF; local only //setupConventionBeanIfMissing(beanName); return super.containsBeanDefinition(beanName); } @Override protected synchronized RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { if (super.containsBeanDefinition(beanName)) { return super.getMergedLocalBeanDefinition(beanName); } final Class<?> type = getResolvedType(beanName); if (type != null) { RootBeanDefinition rootBeanDefinition = mergedBeanDefinitions.get(type); if (rootBeanDefinition == null) { rootBeanDefinition = new RootBeanDefinition(type); rootBeanDefinition.overrideFrom(getBeanDefinition(beanName)); // Are these next two really necessary ??? rootBeanDefinition.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); rootBeanDefinition.setScope(getAnnotatedScope(type)); mergedBeanDefinitions.put(type, rootBeanDefinition); } return rootBeanDefinition; } return null; } @Override public synchronized BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { if (super.containsBeanDefinition(beanName)) { return super.getBeanDefinition(beanName); } final Class<?> resolvedType = getResolvedType(beanName); return getOrCreateBeanDefinition(beanName, resolvedType); } protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { final Class<?> resolvedType = getResolvedType(beanName); if (resolvedType != null) return resolvedType; return super.predictBeanType( beanName, mbd, typesToMatch); } private void setupConventionBeanIfMissing(String name) { if (!super.containsBeanDefinition(name)) { registerByDirectNameToClassMapping(name); } } private Class<?> getResolvedType(String s) throws NoSuchBeanDefinitionException { final Class aClass = resolveImplClass(s); return aClass != null && candidateEvaluator.isBean(aClass) ? aClass : null; } private static class CacheMiss { } private Class resolveImplClass(final String beanName) { Class aClass = cachedConventionResolutions.get(beanName); if (aClass != null && !CacheMiss.class.equals(aClass)) return aClass; aClass = nameToClassResolver.resolveBean(beanName, candidateEvaluator); cachedConventionResolutions.put(beanName, aClass != null ? aClass : CacheMiss.class); return aClass; } private Class getCacheEntry(Class key) { return cachedConventionResolutions.get(beanNameFromClass(key)); } private boolean isCacheMiss(Class cacheResult) { return CacheMiss.class.equals(cacheResult); } private Class resolveClass(final Class beanClass) { return resolveImplClass(beanNameFromClass(beanClass)); } private String beanNameFromClass(final Class beanClass) { return beanClass.getName(); } private Object instantiate(Class aClass) throws BeansException { return doGetBean(aClass.getName(), null, null, false); } private AnnotatedBeanDefinition getOrCreateBeanDefinition(String beanName, Class<?> resolvedType) { AnnotatedBeanDefinition beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition == null) { beanDefinition = createScannedBeanDefinition(resolvedType); beanDefinitionMap.put(beanName, beanDefinition); } return beanDefinition; } private ScannedGenericBeanDefinition createScannedBeanDefinition(Class<?> resolvedType) { final ScannedGenericBeanDefinition rootBeanDefinition = getScannedBeanDefinition(resolvedType); rootBeanDefinition.applyDefaults(this.beanDefinitionDefaults); processCommonDefinitionAnnotations(rootBeanDefinition); rootBeanDefinition.setScope(getAnnotatedScope(resolvedType)); return rootBeanDefinition; } /*on beanDefinition = beanDefinitionMap.get(beanName); if (beanDefinition != null) return beanDefinition; final ScannedGenericBeanDefinition rootBeanDefinition = getScannedBeanDefinition(type); rootBeanDefinition.applyDefaults(this.beanDefinitionDefaults); processCommonDefinitionAnnotations(rootBeanDefinition); rootBeanDefinition.setScope(getAnnotatedScope(type)); beanDefinitionMap.put(type, rootBeanDefinition); return rootBeanDefinition; } */ private ScannedGenericBeanDefinition getScannedBeanDefinition(Class clazz) { try { MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(clazz.getName()); return new ScannedGenericBeanDefinition(metadataReader); } catch (IOException e) { throw new RuntimeException(e); } } private void registerBeanByResolvedType(String beanName, Class<?> resolvedType) { if (resolvedType == null) return; final BeanDefinition beanDefinition = getOrCreateBeanDefinition(beanName, resolvedType); BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName); ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(beanDefinition); ScopedProxyMode scopedProxyMode = scopeMetadata.getScopedProxyMode(); if (!scopedProxyMode.equals(ScopedProxyMode.NO)) { definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, this, scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS)); } registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); } // Ripped from ActionConfigUtils static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) { if (abd.getMetadata().isAnnotated(Primary.class.getName())) { abd.setPrimary(true); } if (abd.getMetadata().isAnnotated(Lazy.class.getName())) { Boolean value = (Boolean) abd.getMetadata().getAnnotationAttributes(Lazy.class.getName()).get("value"); abd.setLazyInit(value); } if (abd.getMetadata().isAnnotated(DependsOn.class.getName())) { String[] value = (String[]) abd.getMetadata().getAnnotationAttributes(DependsOn.class.getName()).get("value"); abd.setDependsOn(value); } } public AutowireCandidateResolver getAutowireCandidateResolver() { return this.qualifierAnnotationAutowireCandidateResolver; } protected Map<String, Object> findAutowireCandidates( String beanName, Class requiredType, DependencyDescriptor descriptor) { // System.out.println("beanName = " + beanName + ", requiredType" + requiredType); String[] candidateNames = getCandidateNames(requiredType, descriptor); Map<String, Object> result = new LinkedHashMap<String, Object>(candidateNames.length); for (Class autowiringType : this.resolvableDependenciesLocalCache.keySet()) { if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = this.resolvableDependenciesLocalCache.get(autowiringType); autowiringValue = resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } } for (String candidateName : candidateNames) { if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, descriptor)) { result.put(candidateName, getBean(candidateName)); } } return result; } public void registerResolvableDependency(Class dependencyType, Object autowiredValue) { Assert.notNull(dependencyType, "Type must not be null"); this.resolvableDependenciesLocalCache.put(dependencyType, autowiredValue); super.registerResolvableDependency(dependencyType, autowiredValue); } private final ConcurrentHashMap<Class, String[]> typeCache = new ConcurrentHashMap<Class, String[]>(); private String[] getCandidateNames(Class requiredType, DependencyDescriptor descriptor) { String[] strings = typeCache.get(requiredType); if (strings != null) { return strings; } strings = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); typeCache.put(requiredType, strings); return strings; } /** * Resolve the given autowiring value against the given required type, * e.g. an {@link org.springframework.beans.factory.ObjectFactory} value to its actual object result. * * @param autowiringValue the value to resolve * @param requiredType the type to assign the result to * @return the resolved value */ public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { autowiringValue = Proxy.newProxyInstance(requiredType.getClassLoader(), new Class[]{requiredType}, new ObjectFactoryDelegatingInvocationHandler(factory)); } else { return factory.getObject(); } } return autowiringValue; } protected void resetBeanDefinition(String beanName) { clearTypeBasedCaches(); super.resetBeanDefinition(beanName); } /** * Reflective InvocationHandler for lazy access to the current target object. */ private static class ObjectFactoryDelegatingInvocationHandler implements InvocationHandler, Serializable { private final ObjectFactory objectFactory; public ObjectFactoryDelegatingInvocationHandler(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0]); } else if (methodName.equals("hashCode")) { // Use hashCode of proxy. return System.identityHashCode(proxy); } else if (methodName.equals("toString")) { return this.objectFactory.toString(); } try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } }